/*
 * Copyright (c) 2025 Huawei Device, Inc. Ltd. and <马弓手>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Zip.h"
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>

REGISTER_PLUGIN_CLASS(Zip)
bool Zip::execute(const string&  action, cJSON* args, CallbackContext cbc)
{
    if(action == "unzip") {
        m_last_progress = -1;
        m_cbc = cbc;
        m_args = cJSON_Duplicate(args, 1);
        m_pZip.setZip(this);
        m_pZip.Start(NULL);
    }
    return true;
}

void Zip::UnZip::Execute(void* arg)
{
    cJSON* args = m_pZip->m_args;
    string strSource = cJSON_GetArrayItem(args, 0)->valuestring;
    string strTarget = cJSON_GetArrayItem(args, 1)->valuestring;
    m_pZip->extract_zip(strSource.c_str(), strTarget.c_str());
    cJSON_Delete(args);
}

// 解压ZIP文件
int Zip::extract_zip(const char *zip_filename, const char *target_dir) 
{
    unzFile uf;
    unsigned long total_bytes = 0;
    unsigned long bytes_processed = 0;
    char filename_inzip[Zip::const_max_path];
    char full_path[Zip::const_max_path];
    void *buf = NULL;
    int err = 0;
    
    char buffer_error[4096];

    // 打开ZIP文件
    uf = unzOpen(zip_filename);
    if (!uf) {
        m_cbc.error("Zip file does not exist");
        return -1;
    }

    // 获取总解压大小
    total_bytes = get_uncompressed_size(uf);
    if (total_bytes == 0) {
        m_cbc.error("Zip file size is zero");
        unzClose(uf);
        return -1;
    }
    
    // 分配缓冲区
    buf = malloc(Zip::const_buffer_size);
    if (!buf) {
        m_cbc.error("malloc memory error");
        unzClose(uf);
        return -1;
    }

    // 定位到第一个文件
    err = unzGoToFirstFile(uf);
    if (err != UNZ_OK) {
        m_cbc.error("Could not open first file of zip");
        free(buf);
        unzClose(uf);
        return -1;
    }

    // 遍历并解压所有文件
    do {
        unz_file_info file_info;
        
        // 获取当前文件信息
        err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
        if (err != UNZ_OK) {
            sprintf(buffer_error, "Could not read file(error: %d)", err);
            m_cbc.error(buffer_error);
            break;
        }

        // 跳过目录项
        if (filename_inzip[strlen(filename_inzip) - 1] == '/') {
            continue;
        }

        // 构建完整目标路径
        snprintf(full_path, sizeof(full_path), "%s/%s", target_dir, filename_inzip);

        // 创建必要的目录
        if (create_parent_dirs(full_path) != 0) {
            break;
        }

        // 打开ZIP中的文件
        err = unzOpenCurrentFile(uf);
        if (err != UNZ_OK) {
            sprintf(buffer_error, "Could not open file %s (error: %d)", filename_inzip, err);
            m_cbc.error(buffer_error);
            break;
        }

        // 创建目标文件
        FILE *fout = fopen(full_path, "wb");
        if (!fout) {
            sprintf(buffer_error, "Could not create file %s", full_path);
            m_cbc.error(buffer_error);
            unzCloseCurrentFile(uf);
            break;
        }

        // 解压文件内容
        do {
            err = unzReadCurrentFile(uf, buf, Zip::const_buffer_size);
            if (err < 0) {
                sprintf(buffer_error, "Could not read file %s (error: %d)", filename_inzip, err);
                m_cbc.error(buffer_error);
                break;
            }
            
            if (err > 0) {
                if (fwrite(buf, err, 1, fout) != 1) {
                    sprintf(buffer_error, "Could not write file %s", full_path);
                    m_cbc.error(buffer_error);
                    err = UNZ_ERRNO;
                    break;
                }
                
                // 更新已处理字节数
                bytes_processed += err;
                
                // 更新进度
                show_progress(filename_inzip, 
                            (double)bytes_processed / total_bytes * 100.0, 
                            bytes_processed, total_bytes);
            }
        } while (err > 0);

        fclose(fout);
        unzCloseCurrentFile(uf);

        // 设置文件权限
        chmod(full_path, file_info.external_fa >> 16);

        // 设置文件修改时间
        struct utimbuf ut;
        struct tm tm;
        tm.tm_sec = file_info.tmu_date.tm_sec;
        tm.tm_min = file_info.tmu_date.tm_min;
        tm.tm_hour = file_info.tmu_date.tm_hour;
        tm.tm_mday = file_info.tmu_date.tm_mday;
        tm.tm_mon = file_info.tmu_date.tm_mon;
        tm.tm_year = file_info.tmu_date.tm_year;
        tm.tm_isdst = -1;
        
        ut.actime = ut.modtime = mktime(&tm);
        utime(full_path, &ut);

    } while (unzGoToNextFile(uf) == UNZ_OK);
    
    // 清理资源
    free(buf);
    unzClose(uf);

    if(err != UNZ_END_OF_LIST_OF_FILE && err != UNZ_OK) {
        sprintf(buffer_error, "Unzip failed (Error: %d)", err);
        m_cbc.error(buffer_error);
        return -1;
    }
    return 0;
}

// 创建目录及其父目录
int Zip::create_parent_dirs(const char *path) 
{
    char tmp[Zip::const_max_path];
    char *p = NULL;
    size_t len;

    snprintf(tmp, sizeof(tmp), "%s", path);
    len = strlen(tmp);
    
    // 去除末尾的'/'
    if (tmp[len - 1] == '/') {
        tmp[len - 1] = 0;
    }

    // 逐级创建目录
    for (p = tmp + 1; *p; p++) {
        if (*p == '/') {
            *p = 0;
            if (access(tmp, F_OK) != 0) {
                if (mkdir(tmp, 0755) != 0) {
                    perror("无法创建目录");
                    return -1;
                }
            }
            *p = '/';
        }
    }
    
    return 0;
}

// 获取ZIP文件中所有文件的总大小
unsigned long Zip::get_uncompressed_size(unzFile uf) 
{
    unz_global_info gi;
    unsigned long total_size = 0;
    
    if (unzGetGlobalInfo(uf, &gi) != UNZ_OK) {
        return 0;
    }

    // 遍历所有文件获取未压缩大小
    if (unzGoToFirstFile(uf) != UNZ_OK) {
        return 0;
    }

    do {
        unz_file_info file_info;
        if (unzGetCurrentFileInfo(uf, &file_info, NULL, 0, NULL, 0, NULL, 0) == UNZ_OK) {
            total_size += file_info.uncompressed_size;
        }
    } while (unzGoToNextFile(uf) == UNZ_OK);

    return total_size;
}

// 显示进度条
void Zip::show_progress(const char *filename, 
                  double progress, 
                  unsigned long bytes_processed,
                  unsigned long total_bytes) 
{
    int current_progress = (int)progress;
    // 只在进度变化时更新显示
    if (current_progress != m_last_progress) {
        cJSON* progress = cJSON_CreateObject();
        cJSON_AddNumberToObject(progress, "loaded", bytes_processed);
        cJSON_AddNumberToObject(progress, "total", total_bytes);
        PluginResult pluginResult(PluginResult::OK, progress);
        pluginResult.setKeepCallback(true);
        m_cbc.sendPluginResult(pluginResult);
        m_last_progress = current_progress;
    }
}